import matplotlib.pyplot as plt
import geopandas
from geopandas import GeoSeries
from shapely.geometry import Polygon, box
from random import randint
from functools import partial
from collections import OrderedDict


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return "(%s,%s)" % (self.x, self.y)

    def __lt__(self, other):

        if self.x == other.x:
            return self.y < other.y
        else:
            return self.x < other.x

    __repr__ = __str__


class Event:
    LL = 0
    UR = 1

    def __init__(self, index, ctype):
        self.index = index
        self.type = ctype

    def __str__(self):
        return "event(%s %s)" % (self.index, "LL" if self.type == 0 else "UR")

    __repr__ = __str__


class Box:
    def __init__(self, p1, p2):
        self.p1 = p1
        self.p2 = p2
        self.splits = [p1]

    def __iter__(self):
        yield self.p1
        yield self.p2

    def __str__(self):
        return "[%s,%s]" % (self.p1, self.p2)

    __repr__ = __str__

    def __lt__(self, other):
        return self.p1 < other.p1


def get_random_boxes(num):
    '''
    :param num: box num
    :return:  random generated box array
    '''
    boxes = []
    for v in range(num):
        w = randint(50, 100)
        h = randint(50, 100)
        x = randint(10, 200)
        y = randint(10, 200)
        tbox = Box(Point(x, y), Point(x + w, y + h))
        boxes.append(tbox)
    return boxes


class VEdge:
    def __iter__(self, p1, index):
        self.p1 = p1
        self.index = index


def test(boxes):
    events_v = []
    events_h = []
    h_keys = set()
    v_keys = set()
    v_keys.add(0)
    v_keys.add(300)
    h_keys.add(0)
    h_keys.add(300)
    for index, cbox in enumerate(boxes):
        events_v.append(Event(index, Event.LL))
        events_v.append(Event(index, Event.UR))
        events_h.append(Event(index, Event.LL))
        events_h.append(Event(index, Event.UR))
        h_keys.add(cbox.p1.x)
        h_keys.add(cbox.p2.x)
        v_keys.add(cbox.p1.y)
        v_keys.add(cbox.p2.y)

    def sort_op_x(boxes, ev):
        if ev.type == Event.LL:
            return boxes[ev.index].p1.x
        else:
            return boxes[ev.index].p2.x

    f_sort_op_x = partial(sort_op_x, boxes)

    def sort_op_y(boxes, ev):
        if ev.type == Event.LL:
            return boxes[ev.index].p1.y
        else:
            return boxes[ev.index].p2.y

    f_sort_op_y = partial(sort_op_y, boxes)
    sorted_events_v = sorted(events_v, key=f_sort_op_x)
    sorted_events_h = sorted(events_h, key=f_sort_op_y)

    print("sorted box by p1", sorted(boxes))
    print("sort v events",
          [(boxes[v.index].p1.x if v.type == Event.LL else boxes[v.index].p2.x, v) for v in sorted_events_v])
    print("sort h events",
          [(boxes[v.index].p1.y if v.type == Event.LL else boxes[v.index].p2.y, v) for v in sorted_events_h])
    cset = {}

    curr_x = 0
    matrix = OrderedDict()

    fractures = []
    for vi, v_ev in enumerate(sorted_events_v):
        box = boxes[v_ev.index]

        matrix[curr_x] = OrderedDict()
        ovl_count = 0
        pre_x = curr_x
        if v_ev.type == Event.LL:
            curr_x = box.p1.x
        else:
            curr_x = box.p2.x
        hlines = []
        for h_ev in sorted_events_h:
            h_box = boxes[h_ev.index]
            if h_ev.type == Event.UR:
                curr_y = h_box.p2.y
            else:
                curr_y = h_box.p1.y
            if h_ev.index in cset and cset[h_ev.index] == 1:
                if h_ev.type == Event.UR:
                    if ovl_count == 1:
                        hlines.append(curr_y)
                    ovl_count -= 1
                else:
                    ovl_count += 1
            if ovl_count > 0:
                matrix[pre_x][curr_y] = 1
                hlines.append(curr_y)
        if len(hlines) > 1:
            pp = hlines[0]
            pnts = []
            for hl in hlines[1:]:
                pnts.append([pp, hl])
                pp = hl

            fractures += [((pre_x, p[0]), (curr_x, p[1])) for p in pnts]

        if v_ev.type == Event.LL:
            cset[v_ev.index] = 1
        else:
            cset[v_ev.index] = 0

    matrix[curr_x] = OrderedDict()
    print('matrix', matrix)

    h_keys = sorted(list(h_keys))
    v_keys = sorted(list(v_keys))

    indent = '\t'

    print("array size %s x %s" % (len(h_keys), len(v_keys)))
    for colname in ["{:>5}".format("")] + ["y"] * len(v_keys):
        print(colname, end=indent)
    print()
    for colname in ["{:>5}".format("")] + v_keys:
        print(colname, end=indent)
    print()
    for row_key in h_keys:
        print("x{:>4}".format(row_key), end=indent)
        for col_key in v_keys:
            print(matrix[row_key].get(col_key, 0) if row_key in matrix else 0, end=indent)
        print()

    return boxes, matrix, fractures


boxes = get_random_boxes(6)
gboxes = [box(b.p1.x, b.p1.y, b.p2.x, b.p2.y) for b in boxes]
g = GeoSeries(gboxes)
fig, axs = plt.subplots(2, 1 ,figsize=(5, 10))
t = g.plot(alpha=1, facecolor='none', edgecolor='k',ax=axs[0])




boxes, matrix, fs = test(boxes)
plt.xticks(range(0, 300, 10), rotation=90, fontsize=10)
plt.yticks(range(0, 300, 10))
plt.xlim(0,300)
for index, vline in enumerate(matrix.keys()):
    sub = [key for key in matrix[vline].keys()]
    if len(sub) > 0:
        y = min(sub) - 10 if index % 2 == 0 else max(sub) + 10
        t.vlines(vline, 0, max(sub), linestyles='dashed', color='red')
    else:
        y = 0
        t.vlines(vline, 0, 200, linestyles='dashed', color='red')
    t.annotate(vline, (vline, y), xytext=(vline, y), fontsize=8)

gfractures=[box(*bp[0],*bp[1]) for bp in fs]
print(len(gfractures))
gf=GeoSeries(gfractures)

t = gf.plot(alpha=0.1,  edgecolor='r',ax=axs[1],cmap='tab10')
plt.show()
